package com.xishu.service;

import com.xishu.client.ZkClient;
import com.xishu.config.Constant;
import com.xishu.util.DateUtil;
import com.xishu.util.MongoId;
import com.xishu.util.Tools;
import org.apache.curator.framework.recipes.locks.InterProcessLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static com.xishu.config.Constant.LEADER_LOCK;

/**
 * 选主
 * <p>
 * 每个节点或者进程，启动的时候，会随机生一个字符串
 * 主节点目前是20秒延期一次，如果其它节点在轮询的时候，发现主节点超过2分钟没有更新了，那么去争取成为主节点。
 * 选主过程，如果发现当前没有主节点，或者主节点的租期已经超过了2分钟，那么所有节点去竞选，竞选成功后，写入自己的随机字符串，并添加主节点租期的时间
 */
public class LeaderService implements Tools {
    public final static String NODE_ID = MongoId.generateId();
    private static Logger logger = LoggerFactory.getLogger(LeaderService.class);
    private static LeaderService instance = new LeaderService();


    private LeaderService() {
        logger.info("current node id {}", NODE_ID);
    }

    public static LeaderService getInstance() {
        return instance;
    }

    /**
     * 获取主节点的租期信息
     * 包含节点的ID，以及租期的起始时间
     *
     * @return
     */
    public String getLeaderNonce() throws Exception {
        return ZkClient.getInstance().getData(Constant.LEADER_NONCE, "");
    }

    /**
     * 写租期的时间
     *
     * @return
     */
    public void writeLeaderNonce() throws Exception {
        ZkClient.getInstance().setData(Constant.LEADER_NONCE, String.format("%s:%s", NODE_ID, System.currentTimeMillis()));
    }

    /**
     * 获取主节点的ID
     *
     * @param nonce
     * @return
     */
    public String getLeaderId(String nonce) {
        String[] split = nonce.split(":");
        if (split != null && split.length == 2) {
            return split[0];
        }

        logger.info("can not get the leader id {}", nonce);
        return "";
    }

    /**
     * 获取锁定时间
     *
     * @param nonce
     * @return
     */
    public long getLockTime(String nonce) {
        String[] split = nonce.split(":");
        if (split != null && split.length == 2) {
            return Long.valueOf(split[1]);
        }

        logger.info("can not get the leader id {}", nonce);
        return 0l;
    }

    /**
     * 判断当前节点是否是主节点
     * 首先ID要一样
     * 如果ID不一样，那么当前节点肯定不是主节点
     * 如果ID一样，还要判断是否在租期范围内
     *
     * @param nonceReq
     * @return
     */
    public boolean isLeader(String nonceReq) {
        String leaderId = getLeaderId(nonceReq);
        if (!equals(leaderId, NODE_ID)) {
            return false;
        }

        long lockTime = getLockTime(nonceReq);
        return lockTimeValid(lockTime);
    }

    /**
     * 判断锁定时间是否有效
     *
     * @param lockTime
     * @return
     */
    public boolean lockTimeValid(long lockTime) {
        long currentTimeMillis = System.currentTimeMillis();
        return currentTimeMillis - lockTime <= DateUtil.ONE_MINUTE * 2;
    }

    /**
     * 选举主节点，只有当前节点选举成主节点之后，才能够执行主节点的操作
     *
     * @return
     */
    public boolean elect() {
        LeaderService leaderService = LeaderService.getInstance();
        DistributeLockService lockService = DistributeLockService.getInstance();

        try {
            String leaderNonce = leaderService.getLeaderNonce();

            if (isEmpty(leaderNonce)) {
                logger.info("leaderNonce is null");
                InterProcessLock interProcessLock = lockService.tryLock(LEADER_LOCK);
                leaderNonce = leaderService.getLeaderNonce();

                //如果同时去竞选主失败了，那么返回，并且释放锁
                if (isNotEmpty(leaderNonce) && !leaderService.isLeader(leaderNonce)) {
                    interProcessLock.release();
                    logger.info("election failed", LeaderService.NODE_ID);
                    return false;
                }

                //选主成功，那么写nonce
                leaderService.writeLeaderNonce();
                interProcessLock.release();
                logger.info("election success", LeaderService.NODE_ID);
                return true;
            } else if (leaderService.isLeader(leaderNonce)) {
                //如果当前节点是主节点，续租
                logger.info("current node is master node {}", LeaderService.NODE_ID);
                leaderService.writeLeaderNonce();
                return true;
            } else {
                //主节点曾经存在，但是需要重新选举
                InterProcessLock interProcessLock = lockService.tryLock(LEADER_LOCK);
                leaderNonce = leaderService.getLeaderNonce();
                logger.info("leaderNonce is {}", leaderNonce);
                long lockTime = leaderService.getLockTime(leaderNonce);
                //租期依然是无效的，那么当前节点就是成功选举的主节点
                if (!leaderService.lockTimeValid(lockTime)) {
                    logger.info("election success", LeaderService.NODE_ID);
                    leaderService.writeLeaderNonce();
                    interProcessLock.release();
                    return true;
                } else {
                    //已经有其它节点获取了锁并且写入了租期，那么这个节点退出
                    logger.info("election failed", LeaderService.NODE_ID);
                    interProcessLock.release();
                    return false;
                }
            }
        } catch (Exception e) {
            logger.error("e", e);
            return false;
        }
    }
}
